package com.appbee.core.client.data;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.Range;

/**
 * After fetching data needed for visual range we fetch data with maxsize =
 * buffersize so that it is ready when user go's to næxt page.
 * 
 * @author jakobmarrunarsson
 * 
 * @param <T>
 */
public class BufferedDataProvider<T>
{

	private IDataUpdater<T> updater;
	private IFetcher<T> fetcher;
	private int bufferSize = 100;
	private final List<Fetch<T>> fetchList = new ArrayList<Fetch<T>>();
	private Scheduler scheduler;
	private final int cacheTimeMs = 1000 * 60 * 5;// 5 mín
	private Object filter;
	private InMemorySorter<T> sorter;

	public void setBufferSize(int bufferSize)
	{
		this.bufferSize = bufferSize;
	}

	public void setFetcher(IFetcher<T> fetcher)
	{
		this.fetcher = fetcher;
	}

	public void setUpdater(IDataUpdater<T> updater)
	{
		this.updater = updater;
	}

	public void onRangeChange(HasData<T> display)
	{
		onRangeChange(display, null);
	}
	public void onRangeChange(HasData<T> display, ISortInfo sortInfo)
	{
		Range visibleRange = getDataRange(display);

		Fetch<T> fetch = findFetch(visibleRange);
		if (fetch == null)
		{
			fetch = createFetch(sortInfo, visibleRange);
		}
		fetch.doFetch(visibleRange);
	}

	public void clear()
	{
		fetchList.clear();
	}

	private Fetch<T> createFetch(ISortInfo sortInfo, Range visibleRange)
	{
		Fetch<T> fetch;
		// Calculate start index
		int start = (visibleRange.getStart() / bufferSize) * bufferSize;
		// If the range is on the buffer border then start from visible
		// range
		if ((start + bufferSize) < (visibleRange.getStart() + visibleRange
				.getLength()))
		{
			start = visibleRange.getStart();
		}

		// Create fetch
		final Fetch<T> f = new Fetch<T>(new Range(start, bufferSize),
				getFilterHashCode(), updater, fetcher, sortInfo);
		fetch = f;
		fetchList.add(fetch);
		getScheduler().scheduleFixedDelay(new Scheduler.RepeatingCommand()
		{

			@Override
			public boolean execute()
			{
				fetchList.remove(f);
				return false;
			}
		}, cacheTimeMs);
		return fetch;
	}

	private Fetch<T> findFetch(Range visibleRange)
	{
		for (Fetch<T> fetch : fetchList)
		{
			if (new RangeWrap(fetch.getRange()).contains(visibleRange)
					&& fetch.getFilterHashCode() == getFilterHashCode())
				return fetch;
		}
		return null;
	}

	private Fetch<T> findFetch(int index)
	{
		for (Fetch<T> fetch : fetchList)
		{
			if (new RangeWrap(fetch.getRange()).contains(index)
					&& fetch.getFilterHashCode() == getFilterHashCode())
				return fetch;
		}
		return null;
	}


	private int getFilterHashCode()
	{
		return filter == null ? 0 : filter.hashCode();
	}


	public void setScheduler(Scheduler scheduler)
	{
		this.scheduler = scheduler;

	}

	public Scheduler getScheduler()
	{
		if (scheduler == null)
			scheduler = Scheduler.get();
		return scheduler;
	}

	public void setFilter(Object filter)
	{
		this.filter = filter;
	}

	public List<Fetch<T>> getFetchList()
	{
		return fetchList;
	}

	public void setValue(int i, T foo)
	{
		Fetch<T> fetch = findFetch(i);
		if (fetch != null)
		{
			fetch.getData().set(i - fetch.getRange().getStart(), foo);
			fetch.doFetch(fetch.getRange());
		}
	}

	public void onSort(HasData<T> display, ISortInfo sortInfo)
	{
		Range visibleRange = getDataRange(display);

		Fetch<T> fetch = findFetch(visibleRange);
		if (fetch != null && fetch.isExact() && sorter != null)
		{
			sorter.sort(fetch.getData(), sortInfo);
			fetch.doFetch(visibleRange);
			return;
		}
		if (fetch != null)
			fetchList.remove(fetch);
		createFetch(sortInfo, visibleRange).doFetch(visibleRange);

	}

	private Range getDataRange(HasData<T> display)
	{
		Range visibleRange = display.getVisibleRange();
		// Never fetch negative index
		if (visibleRange.getStart() < 0)
			visibleRange = new Range(0, visibleRange.getLength());
		return visibleRange;
	}

	public void setInMemorySorter(InMemorySorter<T> sorter)
	{
		this.sorter = sorter;
	}

}
